#include "sbalance.h"
#include <string.h>

/* rand [1,10] 
	use : j = 1 + (int) (10.0 * (rand() / (RAND_MAX + 1.0))); 
	not use: j = 1 + (rand() % 10); 
*/
unsigned int sb_sdbm_hash(char *str)
{
        unsigned int hash = 0;
        while(*str) {
                hash = (*str++) + (hash << 6) + (hash << 16) - hash;
        }
        return hash;
}

/* return ms */
int sb_time_difference(struct timeval *a, struct timeval *b)
{
	int i = a->tv_sec * 1000 + a->tv_usec / 1000;	
	int j = b->tv_sec * 1000 + b->tv_usec / 1000;	

	return i - j;
}

void sb_set_server_failed(struct sb_cycle_env *env, struct sb_conf_server *server)
{
	server->fails++;
	memcpy(&server->find_unhealth_time, &cache_time, sizeof(struct timeval));
}

void sb_set_server_ok(struct sb_conf_server *server)
{
	server->fails = 0;
	server->try_connect = 0;
}

int sb_check_server_available(struct sb_cycle_env *env, struct sb_conf_server *server)
{
	if (server->fails < server->max_fails) {
		return SB_OK;
	}
	else {  /* fails >= max_fails */
		/* timeout */
		if (sb_time_difference(&cache_time, &server->find_unhealth_time) > server->fail_timeout) {
			server->try_connect = 1;
			return SB_OK;
		}
	}
	
	return SB_ERROR;
}

struct sb_conf_server* sb_load_balance_rr(struct sb_cycle_env *env, struct sb_forward_rule *rule)
{
        int i;
        struct sb_conf_server *server = NULL;

	for (i = 0; i < rule->server_num; i++) {
		if (rule->current_server_index >= rule->server_num)
			rule->current_server_index = 0;

		server = &rule->server_addr[rule->current_server_index];
		if (sb_check_server_available(env, server) == SB_OK) {
			rule->current_server_index++;
			return server;
		}

		rule->current_server_index++;
	}	

	return NULL;
}

struct sb_conf_server* sb_load_balance_random(struct sb_cycle_env *env, struct sb_forward_rule *rule)
{
        int index, i;
        struct sb_conf_server *server = NULL;

	srand((unsigned int)cache_time.tv_sec * 13 + (unsigned int)cache_time.tv_usec * 19);

	for (i = 0; i < rule->server_num * 10; i++) {
		index = rand() % rule->server_num;
		server = &rule->server_addr[index];
		if (sb_check_server_available(env, server) == SB_OK)
			return server;
	}
	
	return NULL;
}

struct sb_conf_server* sb_load_balance_weight(struct sb_cycle_env *env, struct sb_forward_rule *rule)
{
	int index, i;
	int current_weight = 0;
	struct sb_conf_server* server = NULL;

	index = 1 + rand() % rule->server_total_weight;
	
	for (i = 0; i < rule->server_num; i++) {
		server = &rule->server_addr[i]; 
		if (current_weight <= index &&  index <= current_weight + server->weight) {
			if (sb_check_server_available(env, server) == SB_OK) {
				return server;	
			}

			return NULL;
		}
		else {
			current_weight += server->weight;
		}
	}

	return NULL;
}

struct sb_conf_server* sb_load_balance_iphash(struct sb_cycle_env *env, struct sb_forward_rule *rule, char *ip_port)
{
	int index;
        struct sb_conf_server* server = NULL;

	index = sb_sdbm_hash(ip_port) % rule->server_num;
	server = &rule->server_addr[index];
        if (sb_check_server_available(env, server) == SB_OK) {
		return server;
	}
	
	return NULL;
}
/*******
#define BALANCE_MODE_RR                 "rr"		// fail: rr -> rr 
#define BALANCE_MODE_RANDOM             "random"	// fail: random -> random 
#define BALANCE_MODE_WEIGHT             "weight"	// fail: random weight - > random 	
#define BALANCE_MODE_IPHASH             "iphash"	// fail: iphash -> random 
******/
struct sb_conf_server* sb_load_balance_get_server(struct sb_cycle_env *env, struct sb_forward_rule *rule, char *ip_port)
{
        struct sb_conf_server *server = NULL;
	
	if (strcmp(rule->balance_mode, BALANCE_MODE_RR) == 0) {
		return sb_load_balance_rr(env, rule);
        }
	else if (strcmp(rule->balance_mode, BALANCE_MODE_RANDOM) == 0) {
		return sb_load_balance_random(env, rule);
	}
	else if (strcmp(rule->balance_mode, BALANCE_MODE_IPHASH) == 0) {
		if ((server = sb_load_balance_iphash(env, rule, ip_port)) != NULL)
			return server;
		else 
			return sb_load_balance_random(env, rule);
	}
	else if (strcmp(rule->balance_mode, BALANCE_MODE_WEIGHT) == 0) {
		if ((server = sb_load_balance_weight(env, rule)) != NULL)
			return server;
		else 
			return sb_load_balance_random(env, rule);
	}

        return NULL;
}
